/*
 * TileMap.cpp
 *
 *  Created on: Sep 12, 2011
 *      Author: mengwei.chua
 */

#include "TileMap.h"
#include "core/GameConfig.h"
#include "common/CommonUtility.h"
#include "common/Buffer.h"

namespace projectcos
{
namespace tile
{

// Bits on the far end of the 32-bit global tile ID are used for tile flags
const unsigned FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
const unsigned FLIPPED_VERTICALLY_FLAG   = 0x40000000;

TileMap::TileMap(HGE* hge)
: m_HGE(hge)
{
	m_TileSetMap.clear();
	m_TileList.clear();
	m_LayerMap.clear();
}

TileMap::~TileMap()
{
	if(NULL != m_HGE)
	{
		std::map<std::string, HTEXTURE>::iterator it = m_TileSetMap.begin();
		while(it != m_TileSetMap.end())
		{
			m_HGE->Texture_Free(it->second);
			++it;
		}

		for(unsigned int i = 0; i < m_TargetList.size(); ++i)
		{
			m_HGE->Target_Free(m_TargetList[i]);
		}

		m_TargetList.clear();
	}

	m_HGE = NULL;
	m_TileSetMap.clear();
	m_TileList.clear();
	m_LayerMap.clear();
}

bool TileMap::Load(std::string fileName)
{
	TiXmlDocument doc;

	if(!doc.LoadFile(fileName.c_str()))
	{
		return false;
	}

	if(!m_Map.Parse(doc.FirstChildElement("map")))
	{
		return false;
	}

	if(NULL != m_HGE)
	{
		if(!LoadTileTexture())
		{
			return false;
		}

		if(!ExtractLayers())
		{
			return false;
		}
	}
	else
	{
		return false;
	}

	return true;
}

bool TileMap::Render(float x, float y)
{
	unsigned int width = m_Map.GetWidth();
	unsigned int height = m_Map.GetHeight();
	unsigned int tileWidth = m_Map.GetTileWidth();
	unsigned int tileHeight = m_Map.GetTileHeight();

	if(NULL == m_HGE)
	{
		return false;
	}

	for(unsigned int i = 0; i < m_TargetList.size(); ++i)
	{
		HTEXTURE tex = m_HGE->Target_GetTexture(m_TargetList[i]);
		hgeSprite sprite(tex, 0, 0, width * tileWidth, height * tileHeight);
		sprite.SetColor(0xFFFFFFFF);
		sprite.Render(x, y);
	}

	return true;
}

bool TileMap::LoadTileTexture()
{
	std::deque<projectcos::tile::parser::TileSet> tileSetList;
	m_Map.GetTileSets(tileSetList);

	for(unsigned int i = 0; i < tileSetList.size(); ++i)
	{
		std::deque<projectcos::tile::parser::Image> imageList;
		tileSetList[i].GetImages(imageList);

		HTEXTURE tex = 0;

		if(imageList.size() > 0)
		{
			std::string source = RESOURCE_PATH + imageList.front().GetSource();
			tex = m_HGE->Texture_Load(source.c_str());
			if(!tex)
			{
				return false;
			}

			m_TileSetMap[tileSetList[i].GetName()] = tex;
		}

		int actualWidth = m_HGE->Texture_GetWidth(tex, true);
		int actualHeight = m_HGE->Texture_GetHeight(tex, true);
		unsigned int tileWidth = tileSetList[i].GetTileWidth();
		unsigned int tileHeight = tileSetList[i].GetTileHeight();
		unsigned int spacing = tileSetList[i].GetSpacing();
		unsigned int margin = tileSetList[i].GetMargin();
		unsigned int firstGID = tileSetList[i].GetFirstGID();

		int col = (actualWidth - 2 * margin + spacing) / (tileWidth + spacing);
		int row = (actualHeight - 2 * margin + spacing) / (tileHeight + spacing);

		for(int j = 0; j < row * col; ++j)
		{
			int c = j % col;
			int r = j / col;

			hgeSprite sprite(tex, c * (tileWidth + spacing) + margin, r * (tileHeight + spacing) + margin, tileWidth, tileHeight);
			sprite.SetBlendMode(BLEND_DEFAULT);
			sprite.SetColor(0xFFFFFFFF);

			m_TileList.insert(std::make_pair(firstGID+j, sprite));
		}
	}

	return true;
}

bool TileMap::ExtractLayers()
{
	std::deque<projectcos::tile::parser::Layer> layerList;
	m_Map.GetLayers(layerList);

	for(unsigned int i = 0; i < layerList.size(); ++i)
	{
		std::deque<projectcos::tile::parser::Layer::Data> dataList;
		layerList[i].GetData(dataList);

		if(dataList.size() > 0)
		{
			std::string encoding = dataList.front().GetEncoding();
			std::string compression = dataList.front().GetCompression();
			std::string data = dataList.front().GetData();
			projectcos::common::Buffer<unsigned char> buffer;
			projectcos::common::Buffer<unsigned char> uncompressedBuffer;

			if(encoding == "base64" && (compression == "zlib" || compression == "gzip"))
			{
				projectcos::common::CommonUtility::Base64Decode(data, buffer);
				projectcos::common::CommonUtility::Decompress(buffer, uncompressedBuffer);

				if(uncompressedBuffer.GetSize() % 4 != 0)
				{
					return false;
				}

				std::deque<Tile> tileList;
				for(unsigned int j = 0; j < uncompressedBuffer.GetSize(); j += 4)
				{
					unsigned int id = uncompressedBuffer[j]
									  | uncompressedBuffer[j+1] << 8
									  | uncompressedBuffer[j+2] << 16
									  | uncompressedBuffer[j+3] << 24;

					Tile t;

					// Read out the flags
					t.flipHorizontal = (id & FLIPPED_HORIZONTALLY_FLAG);
					t.flipVertical = (id & FLIPPED_VERTICALLY_FLAG);

					// Clear the flags
					id &= ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG);

					t.gid = id;

					tileList.push_back(t);
				}

				m_LayerMap[layerList[i].GetName()] = tileList;
				HTARGET target = RenderToTarget(tileList);
				if(target)
				{
					m_TargetList.push_back(target);
				}
			}
		}
	}

	return true;
}

HTARGET TileMap::RenderToTarget(const std::deque<Tile>& tileList)
{
	HTARGET target = 0;

	if(NULL != m_HGE)
	{
		unsigned int width = m_Map.GetWidth();
		unsigned int height = m_Map.GetHeight();
		unsigned int tileWidth = m_Map.GetTileWidth();
		unsigned int tileHeight = m_Map.GetTileHeight();

		target = m_HGE->Target_Create(width * tileWidth, height * tileHeight, false);

		if(target)
		{
			m_HGE->Gfx_BeginScene(target);
			m_HGE->Gfx_Clear(0);

			for(unsigned int i = 0; i < tileList.size(); ++i)
			{
				int c = i % width;
				int r = i / width;

				Tile t = tileList[i];

				hgeSprite sprite(m_TileList[t.gid]);
				sprite.SetFlip(t.flipHorizontal, t.flipVertical);
				sprite.Render(c * tileWidth, r * tileHeight);
			}

			m_HGE->Gfx_EndScene();
		}
	}

	return target;
}

} // tile
} // projectcos
